搜索 K
Appearance
博客正在加载中...
Appearance
在一个 class 中定义的字段,我们称之为实例字段,它的特点是:每个实例都有独立的字段,各个实例的同名字段互不影响。
但有时候,我们想让一个类的所有实例共享数据,就要使用静态变量(static variable),也称为类变量(class variable)。
例如,我们想知道这个类的对象有多少个,可以使用类变量存储;当每次新创建对象的时候,都给该变量自增。这种变量,如果存储在实例字段里是很难做到的。
静态变量将变量值存储在一个公共的内存地址,它当且仅当在类初次加载时会被初始化。因为它是公共的地址,所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。
静态方法:Java 还支持静态方法,无须创建类的实例就可以调用静态方法(static method)。
总的来说,static 的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。
我们定义一个 Person 类,里面定义一个静态字段。
public class StaticField {
public static void main(String[] args) {
Person ming = new Person("xiao ming", 0);
Person hong = new Person("xiao hong", 0);
ming.number = 88;
System.out.println(hong.number); //88
hong.number = 99;
System.out.println(ming.number); //99
}
}
class Person{
public String name;
public int age;
// 定义静态字段number:
public static int number;
public Person(String name, int age){
this.name = name;
this.age = age;
}
}编译和运行:
> javac StaticField.java -encoding utf8
> java StaticField
88
99对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:可以把静态字段理解为描述 class 本身的字段(非实例字段)。
┌──────────────────┐
ming ──→│Person instance │
├──────────────────┤
│name = "Xiao Ming"│
│age = 12 │
│number ───────────┼──┐ ┌─────────────┐
└──────────────────┘ │ │Person class │
│ ├─────────────┤
├───→ │number = 99 │
┌──────────────────┐ │ └─────────────┘
hong ──→│Person instance │ │
├──────────────────┤ │
│name = "Xiao Hong"│ │
│age = 15 │ │
│number ───────────┼──┘
└──────────────────┘虽然实例可以访问静态字段,但是它们指向的其实都是 Person class的静态字段。所以,所有实例共享一个静态字段。
但是,不推荐用 实例变量.静态字段 去访问静态字段,因为在 Java 程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段,只是因为编译器可以根据实例类型自动转换为 类名.静态字段 来访问静态对象。
推荐用 类名.静态字段 的方式访问静态变量,这样提高了可读性,别人一看可以用类名的方式访问,就知道这是一个静态变量或方法了 :
Person.number = 100; //recommend
System.out.println(Person.number);有静态字段,就有静态方法。用 static 修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。例如:
// static method
public class Main {
public static void main(String[] args) {
Person.setNumber(99);
System.out.println(Person.number);
}
}
class Person {
public static int number;
public static void setNumber(int value) {
number = value;
}
}因为静态方法属于 class 而不属于实例,因此,静态方法内部,无法访问 this 变量,也无法访问实例字段,它只能访问静态字段。
通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。
静态方法经常用于工具类。例如:
Arrays.sort()Math.random()静态方法也经常用于辅助方法。注意到 Java 程序的入口 main() 也是静态方法。为什么 main 方法必须是 static 的,现在就很清楚了。因为程序在执行 main 方法的时候没有创建任何对象,因此只有通过类名来访问
| 实例方法 | 实例数据域 | 静态方法 | 静态数据域 | |
|---|---|---|---|---|
| 实例方法 | √ 调用 | √ 访问 | √ 调用 | √ 访问 |
| 静态方法 | × 调用 | × 访问 | √ 调用 | √ 访问 |
注意:static 关键字不会影响到变量或者方法的作用域,在 Java 中能够影响到访问权限的只有 private、public、protected(包括默认的包访问权限)这几个关键字。例如我们尝试访问 private 和 static 修饰的字段:
public class StaticAccess {
public static void main(String[] args) {
System.out.println(Person.name);
System.out.println(Person.age);
}
}
class Person{
public static String name = "zhangsan";
private static int age = 10;
}编译会报错:
> javac StaticAccess.java
StaticAccess.java:4: 错误: age 在 Person 中是 private 访问控制
System.out.println(Person.age);
^
1 个错误static 关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执行一次。
public class StaticCode {
static{
int num = 1;
System.out.println("static code execute!");
}
public static void main(String[] args) {
StaticCode t = new StaticCode();
System.out.println("Static main execute!");
StaticCode t2 = new StaticCode();
}
}$ javac .\StaticCode.java
$ java StaticCode
static code execute!
Static main execute!static 代码块有什么用?举个离子:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}isBornBoomer 是用来这个人是否是 1946-1964 年出生的,而每次 isBornBoomer 被调用的时候,都会生成 startDate 和 birthDate 两个对象,造成了空间浪费,如果改成这样效率会更好:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}因此,很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。
因为 interface 是一个纯抽象类,所以它不能定义实例字段。但是,interface 是可以有静态字段的,并且静态字段必须为 final 类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}实际上,因为 interface 的字段只能是 public static final 类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:
public interface Person {
// 编译器会自动加上public statc final:
int MALE = 1;
int FEMALE = 2;
}编译器会自动把该字段变为 public static final 类型。
static 关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一
public class StaticInterview1 {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}输出是什么?
分析:虽然在 main 方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static 块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照 static 块的顺序执行的。
test static 1
test static 2public class StaticInterview2 {
static int value = 33;
public static void main(String[] args) {
new StaticInterview2().printValue();;
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}这里面主要考察对 this 和 static 的理解。this 代表什么?this 代表当前对象,那么通过 new StaticInterview2() 来调用 printValue 的话,当前对象就是通过 new StaticInterview2() 生成的对象。而 static 变量是被对象所享有的,因此在 printValue 中的 this.value 的值毫无疑问是 33。在 printValue 方法内部的 value 是局部变量,根本不可能与 this 关联,所以输出结果是 33。
另外,static 不允许用来修饰局部变量(在 C/C++ 中 static 是可以作用域局部变量的),不要问为什么,问就是 Java 语法的规定。
public class StaticInterview1 {
public static void main(String[] args) {
new Test();
}
}
class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}输出结果是什么?请先不要看答案,尝试自己分析下。
分析:
base statictest staticbase constructortest constructor因此,输出结果如下:
base static
test static
base constructor
test constructorpublic class StaticInterview4 {
Person person = new Person("Test");
static {
System.out.println("test static");
}
public StaticInterview4() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person {
static {
System.out.println("person static");
}
public Person(String str) {
System.out.println("person " + str);
}
}
class MyClass extends StaticInterview4 {
Person person = new Person("MyClass");
static {
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}请写出输出结果
分析:类似地,我们还是来想一下这段代码的具体执行过程。
StaticInterview4 类,因此会执行 StaticInterview4 类中的 static 块,输出“test static”new MyClass(),而 MyClass 类还没有被加载,因此需要加载 MyClass 类。在加载 MyClass 类的时候,发现 MyClass 类继承自 StaticInterview4 类,但是由于 StaticInterview4 类已经被加载了,所以只需要加载 MyClass 类,那么就会执行 MyClass 类的中的 static 块,输出“myclass static”MyClass 对象的时候,必须先初始化父类 StaticInterview4 的成员变量,因此会执行 StaticInterview4 中的 Person person = new Person("Test"),而 Person 类还没有被加载过,因此会先加载 Person 类并执行 Person 类中的 static 块,输出 “person static”StaticInterview4 的构造器,完成了父类的初始化,输出 test constructorPerson person = new Person("MyClass"),输出 Person MyClass,myclass constructortest static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructorpublic class Test{
private static Test tester = new Test();
private static int count1;
private static int count2 = 2;
public Test(){
count1++;
count2++;
System.out.println("count1: " + count1);
System.out.println("count2: " + count2);
}
public static Test getTester(){
return tester;
}
public static void main(String[] args) {
Test.getTester();
System.out.println("count1: " + count1);
System.out.println("count2: " + count2);
}
}JAVA 类首次装入时,开始加载静态变量和静态块,也就是说会首先为静态区域分配内存空间,此时 tester、count1、count2 都已经分配空间,其中 tester 为一个引用空间,count1、count2 为默认值 0。
第二步开始执行 private static Test tester = new Test() 这段代码,调用构造器打印出 count1、count2 分别为 1 和 1 。
然后依次执行一下代码 private static int count1; private static int count2 = 2; 此时,count2 被重置为 2,因此如果此时再次打印的话 count1、count2 的值应该为 1 和 2。
count1: 1
count2: 1
count1: 1
count2: 2class的字段;this,但可以访问静态字段和其他静态方法,静态方法常用于工具类和辅助方法。Java 程序运行的时候,会做什么
在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
但有时候,我们想让一个类的所有实例共享数据,就要使用静态变量(static variable),也称为类变量(class variable)。
静态变量将变量值存储在一个公共的内存地址,它当且仅当在类初次加载时会被初始化。因为它是公共的地址,所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。
静态方法:Java还支持静态方法,无须创建类的实例就可以调用静态方法(static method)。
总的来说,static的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。
我们定义一个Person类,里面定义一个静态字段。
public class StaticField {
public static void main(String[] args) {
Person ming = new Person("xiao ming", 0);
Person hong = new Person("xiao hong", 0);
ming.number = 88;
System.out.println(hong.number); //88
hong.number = 99;
System.out.println(ming.number); //99
}
}
class Person{
public String name;
public int age;
// 定义静态字段number:
public static int number;
public Person(String name, int age){
this.name = name;
this.age = age;
}
}编译和运行:
> javac StaticField.java -encoding utf8
> java StaticField
88
99对于静态字段,无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了,原因是静态字段并不属于实例:可以把静态字段理解为描述class本身的字段(非实例字段)。
┌──────────────────┐
ming ──→│Person instance │
├──────────────────┤
│name = "Xiao Ming"│
│age = 12 │
│number ───────────┼──┐ ┌─────────────┐
└──────────────────┘ │ │Person class │
│ ├─────────────┤
├───→ │number = 99 │
┌──────────────────┐ │ └─────────────┘
hong ──→│Person instance │ │
├──────────────────┤ │
│name = "Xiao Hong"│ │
│age = 15 │ │
│number ───────────┼──┘
└──────────────────┘虽然实例可以访问静态字段,但是它们指向的其实都是Person class的静态字段。所以,所有实例共享一个静态字段。
但是,不推荐用实例变量.静态字段去访问静态字段,因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段,只是因为编译器可以根据实例类型自动转换为类名.静态字段来访问静态对象。
推荐用 类名.静态字段 的方式访问静态变量,这样提高了可读性,别人一看可以用类名的方式访问,就知道这是一个静态变量或方法了 :
Person.number = 100; //recommend
System.out.println(Person.number);有静态字段,就有静态方法。用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。例如:
// static method
public class Main {
public static void main(String[] args) {
Person.setNumber(99);
System.out.println(Person.number);
}
}
class Person {
public static int number;
public static void setNumber(int value) {
number = value;
}
}因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。
静态方法经常用于工具类。例如:
Arrays.sort()Math.random()静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问
| 实例方法 | 实例数据域 | 静态方法 | 静态数据域 | |
|---|---|---|---|---|
| 实例方法 | √调用 | √访问 | √调用 | √访问 |
| 静态方法 | ×调用 | ×访问 | √调用 | √访问 |
注意:static关键字不会影响到变量或者方法的作用域,在Java中能够影响到访问权限的只有private、public、protected(包括默认的包访问权限)这几个关键字。例如我们尝试访问private 和 static修饰的字段:
public class StaticAccess {
public static void main(String[] args) {
System.out.println(Person.name);
System.out.println(Person.age);
}
}
class Person{
public static String name = "zhangsan";
private static int age = 10;
}编译会报错:
> javac StaticAccess.java
StaticAccess.java:4: 错误: age 在 Person 中是 private 访问控制
System.out.println(Person.age);
^
1 个错误
static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
public class StaticCode {
static{
int num = 1;
System.out.println("static code execute!");
}
public static void main(String[] args) {
StaticCode t = new StaticCode();
System.out.println("Static main execute!");
StaticCode t2 = new StaticCode();
}
}
$ javac .\StaticCode.java
$ java StaticCode
static code execute!
Static main execute! static代码块有什么用?举个离子:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:
public interface Person {
// 编译器会自动加上public statc final:
int MALE = 1;
int FEMALE = 2;
}编译器会自动把该字段变为public static final类型。
static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一
public class StaticInterview1 {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}输出是什么? 分析:虽然在main方法中没有任何语句,但是还是会输出,原因上面已经讲述过了。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。
test static 1
test static 2
public class StaticInterview2 {
static int value = 33;
public static void main(String[] args) {
new StaticInterview2().printValue();;
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
} 这里面主要考察对this和static的理解。this代表什么?this代表当前对象,那么通过new StaticInterview2()来调用printValue的话,当前对象就是通过new StaticInterview2()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。
另外,static不允许用来修饰局部变量(在C/C++中static是可以作用域局部变量的),不要问为什么,这是Java语法的规定。
public class StaticInterview1 {
public static void main(String[] args) {
new Test();
}
}
class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}输出结果是什么?请先不要看答案,尝试自己分析下。
分析:
base statictest staticbase constructortest constructor因此,输出结果如下:
base static
test static
base constructor
test constructorpublic class StaticInterview4 {
Person person = new Person("Test");
static {
System.out.println("test static");
}
public StaticInterview4() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person {
static {
System.out.println("person static");
}
public Person(String str) {
System.out.println("person " + str);
}
}
class MyClass extends StaticInterview4 {
Person person = new Person("MyClass");
static {
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}请写出输出结果
分析:类似地,我们还是来想一下这段代码的具体执行过程。
StaticInterview4类,因此会执行StaticInterview4类中的static块,输出“test static”new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自StaticInterview4类,但是由于StaticInterview4类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块,输出“myclass static”MyClass对象的时候,必须先初始化父类StaticInterview4的成员变量,因此会执行StaticInterview4中的Person person = new Person("Test"),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,输出 “person static”StaticInterview4的构造器,完成了父类的初始化,输出test constructorPerson person = new Person("MyClass"),输出Person MyClass,myclass constructortest static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructorpublic class Test{
private static Test tester = new Test();
private static int count1;
private static int count2 = 2;
public Test(){
count1++;
count2++;
System.out.println("count1: " + count1);
System.out.println("count2: " + count2);
}
public static Test getTester(){
return tester;
}
public static void main(String[] args) {
Test.getTester();
System.out.println("count1: " + count1);
System.out.println("count2: " + count2);
}
}JAVA 类首次装入时,开始加载静态变量和静态块,也就是说会首先为静态区域分配内存空间,此时 tester、count1、count2 都已经分配空间,其中 tester 为一个引用空间,count1、count2 为默认值 0。
第二步开始执行 private static Test tester = new Test() 这段代码,调用构造器打印出 count1、count2 分别为 1 和 1 。
然后依次执行一下代码 private static int count1; private static int count2 = 2; 此时,count2 被重置为 2,因此如果此时再次打印的话 count1、count2 的值应该为 1 和 2。
count1: 1
count2: 1
count1: 1
count2: 2class的字段;this,但可以访问静态字段和其他静态方法,静态方法常用于工具类和辅助方法。Java程序运行的时候,会做什么